/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.compose.ui.arkui.breakpoint

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf

/**
 * CompositionLocal for providing BreakpointManager to the composition tree.
 * Must be provided at the root level of your Compose hierarchy.
 *
 * Usage:
 * ```
 * CompositionLocalProvider(LocalBreakpointManager provides breakpointManager) {
 *     // Your composable content
 * }
 * ```
 */
val LocalBreakpointManager = staticCompositionLocalOf<BreakpointManager> {
    error("BreakpointManager not provided. Please provide it using CompositionLocalProvider.")
}

/**
 * Composable function that returns a value based on the current width breakpoint.
 * Similar to RN's `useBreakpointValue` hook.
 *
 * The function implements fallback logic: if the value for the current breakpoint is null,
 * it searches backward through smaller breakpoints until a non-null value is found.
 * If no value is found, it returns the `base` value.
 *
 * @param xs Value for extra small breakpoint (< 320vp)
 * @param sm Value for small breakpoint (320-600vp)
 * @param md Value for medium breakpoint (600-840vp)
 * @param lg Value for large breakpoint (840-1440vp)
 * @param xl Value for extra large breakpoint (>= 1440vp)
 * @param base Base/default value used as final fallback
 * @param referenceWidth Optional custom width to calculate breakpoint from (instead of global window width)
 * @return The value corresponding to the current or nearest smaller breakpoint
 *
 * Example:
 * ```
 * val columns = rememberBreakpointValue(
 *     xs = 1,
 *     sm = 2,
 *     md = 3,
 *     lg = 4,
 *     base = 1
 * )
 * ```
 */
@Composable
fun <T> rememberBreakpointValue(
    xs: T? = null,
    sm: T? = null,
    md: T? = null,
    lg: T? = null,
    xl: T? = null,
    base: T,
    referenceWidth: Float? = null
): T {
    val breakpointManager = LocalBreakpointManager.current

    // If referenceWidth is provided, calculate breakpoint directly without subscribing to changes
    val currentBreakpoint = if (referenceWidth != null) {
        remember(referenceWidth) {
            val bpString = breakpointManager.calculateBreakpoint(referenceWidth, isHeight = false)
            WidthBreakpoint.fromString(bpString)
        }
    } else {
        // Subscribe to global breakpoint changes
        val breakpoint by breakpointManager.currentWidthBreakpoint
        breakpoint
    }

    // Implement fallback logic: current → previous → ... → base
    return when (currentBreakpoint) {
        WidthBreakpoint.XL -> xl ?: lg ?: md ?: sm ?: xs ?: base
        WidthBreakpoint.LG -> lg ?: md ?: sm ?: xs ?: base
        WidthBreakpoint.MD -> md ?: sm ?: xs ?: base
        WidthBreakpoint.SM -> sm ?: xs ?: base
        WidthBreakpoint.XS -> xs ?: base
    }
}

/**
 * Composable function that returns a value based on the current height breakpoint (aspect ratio).
 * Similar to RN's `useHeightBreakpointValue` hook.
 *
 * The function implements fallback logic: if the value for the current breakpoint is null,
 * it searches backward through smaller breakpoints until a non-null value is found.
 * If no value is found, it returns the `base` value.
 *
 * @param sm Value for small height breakpoint (aspect ratio < 0.8)
 * @param md Value for medium height breakpoint (aspect ratio 0.8-1.2)
 * @param lg Value for large height breakpoint (aspect ratio >= 1.2)
 * @param base Base/default value used as final fallback
 * @param referenceHeight Optional custom aspect ratio to calculate breakpoint from
 * @return The value corresponding to the current or nearest smaller breakpoint
 *
 * Example:
 * ```
 * val padding = rememberHeightBreakpointValue(
 *     sm = 8.dp,
 *     md = 16.dp,
 *     lg = 24.dp,
 *     base = 16.dp
 * )
 * ```
 */
@Composable
fun <T> rememberHeightBreakpointValue(
    sm: T? = null,
    md: T? = null,
    lg: T? = null,
    base: T,
    referenceHeight: Float? = null
): T {
    val breakpointManager = LocalBreakpointManager.current

    val currentBreakpoint = if (referenceHeight != null) {
        remember(referenceHeight) {
            val bpString = breakpointManager.calculateBreakpoint(referenceHeight, isHeight = true)
            HeightBreakpoint.fromString(bpString)
        }
    } else {
        val breakpoint by breakpointManager.currentHeightBreakpoint
        breakpoint
    }

    return when (currentBreakpoint) {
        HeightBreakpoint.LG -> lg ?: md ?: sm ?: base
        HeightBreakpoint.MD -> md ?: sm ?: base
        HeightBreakpoint.SM -> sm ?: base
    }
}

/**
 * Composable function that subscribes to breakpoint changes and executes a callback.
 * The subscription is automatically cleaned up when the composable leaves the composition.
 *
 * @param onBreakpointChange Callback invoked when breakpoint changes, receives (widthBp, heightBp)
 *
 * Example:
 * ```
 * rememberBreakpointSubscription { widthBp, heightBp ->
 *     Log.d("Breakpoint", "Changed to: $widthBp x $heightBp")
 * }
 * ```
 */
@Composable
fun rememberBreakpointSubscription(
    onBreakpointChange: (widthBreakpoint: String, heightBreakpoint: String) -> Unit
) {
    val breakpointManager = LocalBreakpointManager.current

    DisposableEffect(breakpointManager) {
        val unsubscribe = breakpointManager.subscribeToBreakpoint(onBreakpointChange)
        onDispose {
            unsubscribe()
        }
    }
}

/**
 * Composable function that returns both current width and height breakpoints as a pair.
 *
 * @return Pair of (WidthBreakpoint, HeightBreakpoint)
 *
 * Example:
 * ```
 * val (widthBp, heightBp) = rememberBreakpointState()
 * when {
 *     widthBp == WidthBreakpoint.LG && heightBp == HeightBreakpoint.SM -> {
 *         // Tablet landscape layout
 *     }
 *     else -> {
 *         // Other layouts
 *     }
 * }
 * ```
 */
@Composable
fun rememberBreakpointState(): Pair<WidthBreakpoint, HeightBreakpoint> {
    val breakpointManager = LocalBreakpointManager.current
    val width by breakpointManager.currentWidthBreakpoint
    val height by breakpointManager.currentHeightBreakpoint
    return remember(width, height) { width to height }
}
